package aqua.smile.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


public class SocketChannelClient {

    private final static String DEFAULT_HOST = "127.0.0.1";
    private final static int DEFAULT_PORT = 3456;
    private SocketChannel channel;
    private Socket socket;
    // 分配一个大小为50字节的缓冲区 用于客户端通道的读写
    private final ByteBuffer buffer = ByteBuffer.allocate(50);

    public SocketChannelClient() {
        this(DEFAULT_HOST, DEFAULT_PORT);
    }

    public SocketChannelClient(String host, int port) {
        init(host, port);
    }

    /**
     * 打开通道并设置对等的客户端socket对象
     * 建立与服务端通道的连接
     *
     * @param host
     * @param port
     */
    public void init(String host, int port) {
        try {
            //打开一个客户端通道，同时当前通道并没有与服务端通道建立连接
            channel = SocketChannel.open();
            //获得对等的客户端socket
            socket = channel.socket();
            //配置客户端socket
            setSocket();
            //将通道设置为非阻塞工作方式,如果通道设置我阻塞模式，那么这与直接使用传统Socket通信是一样的
            //channel.configureBlocking(false);
            //异步连接，发起连接之后就立即返回
            //返回true，连接已经建立
            //返回false，后续继续建立连接
            channel.connect(new InetSocketAddress(host, port));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 验证连接是否建立
     */
    public void finishConnect() {
        try {
            while (!channel.finishConnect()) {
                // nothing to do,wait connect
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 验证当前连接是否可用
     */
    public void isConnected() {
        try {
            if (channel == null || !channel.isConnected())
                throw new IOException("channel is broken");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 配置客户端通道对等的Socket
     */
    public void setSocket() {
        try {
            if (socket != null) {
                //设置socket 读取的超时时间5秒
                //socket.setSoTimeout(5000);
                //设置小数据包不再组合成大包发送，也不再等待前一个数据包返回确认消息
                socket.setTcpNoDelay(true);
                //设置如果客户端Socket关闭了，未发送的包直接丢弃
                socket.setSoLinger(true, 0);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //java.lang.IllegalArgumentException
    public void write(String data) {
        byte[] datas = data.getBytes();
        buffer.clear();
        buffer.putInt(datas.length);
        buffer.put(data.getBytes());
        buffer.flip();
        try {
            // write并不一定能一次将buffer中的数据都写入 所以这里要多次写入
            // 当多个线程同时调用同一个通道的写方法时，只有一个线程能工作，其他现在则会阻塞
            while (buffer.hasRemaining()) {
                channel.write(buffer);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void read() {
        try {
            buffer.clear();
            // read方法并不阻塞,如果有数据读入返回读入的字节数，没有数据读入返回0 ，遇到流的末尾返回-1
            // 当然这里和Socket和ServerSocket通信一样 也会存在消息无边界的问题 我们这里就采取简单的读取一次作为示例
            System.out.println("read begin : " + Thread.currentThread().getName());
            channel.read(buffer);
		    /* while(buffer.hasRemaining() && channel.read(buffer) != -1){
				printBuffer(buffer);
			}*/
            buffer.flip();
            printBuffer(buffer);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 输出buffer中的数据
     *
     * @param buffer
     */
    public void printBuffer(ByteBuffer buffer) {
        while (buffer.hasRemaining()) {
            System.out.print((char) buffer.get());
        }
        System.out.println("");
        System.out.println("****** Read end ******");
    }

    /**
     * 判断通道是否打开
     *
     * @return
     */
    public boolean isChannelOpen() {
        try {
            return channel.finishConnect();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 关闭通道
     */
    public void closeChannel() {
        if (channel != null) {
            try {
                channel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        //	client(DEFAULT_HOST,DEFAULT_PORT);
        SocketChannelClient client = new SocketChannelClient();
        client.finishConnect();
        System.out.println("connect success");

        //这里连续进行三次读写，希望模拟的是连续三次客户端方法调用
        for (int i = 0; i < 3; i++) {
            client.write("Hello World 001");
            sleep(5000);
            client.read();
        }

        /*ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            final int index = i;
            executorService.submit(() -> {
                if (index % 2 == 0) {
                    client.read();
                } else {
                    client.write("Hello World 001");
                }
            });
        }*/

        sleep(3000000);

    }

    public static void sleep(long time) {
        try {
            Thread.sleep(time);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
